package com.ruoyi.web.controller.inversion;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.core.CalcThreadManager;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ProcessStopException;
import com.ruoyi.common.utils.ExecUtil;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.SseEmitterUtils;
import com.ruoyi.common.utils.biz.BizModule;
import com.ruoyi.common.utils.biz.CommonUtil;
import com.ruoyi.common.utils.biz.PathUtil;
import com.ruoyi.common.utils.biz.ResultSaveModule;
import com.ruoyi.dm.domain.po.SpaceTargetMeasurePO;
import com.ruoyi.dm.mapper.DataManagementMapper;
import com.ruoyi.dm.service.ISpaceTargetMeasureService;
import com.ruoyi.web.core.config.FileMonitorUtil;
import com.ruoyi.web.vo.InversionOrbitVo;
import common.EAtmosphereModeType;
import common.StAtmosphereDensityData;
import common.StOrbitPoint;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import plugins.LowAtmosphereCalcNative;

import java.io.File;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <p>
 *  大气密度反演 - 最小定轨误差反演
 * </p>
 *
 * @author liuhl
 * @since 2024-08-09
 */
@RestController
@RequestMapping("/inversion/orbit")
@AllArgsConstructor
@Slf4j
@Api(tags = "大气密度反演 - 最小定轨误差反演")
public class InversionMinOrbitController {

    private DataManagementMapper dataManagementMapper;
    private ISpaceTargetMeasureService measureService;

    @PostMapping("/invert")
    @ApiOperation(value = "最小定轨误差反演", notes = "根据时间范围、卫星、模型得到反演结果", httpMethod = "POST")
    public AjaxResult invert(@RequestBody InversionOrbitVo vo) throws Exception {
        String sseId = vo.getSseId()+SecurityUtils.getLoginUser().getUsername();
        vo.setSseId(sseId);

        FutureTask<AjaxResult> task = new FutureTask<>(() ->{
            AjaxResult result = invertInner(vo);
            return result;
        });
        CalcThreadManager.addThread(sseId,task);
        try {
            new Thread(task).start();
            return task.get();
        } catch (InterruptedException | CancellationException exception) {
            throw new ProcessStopException();
        } catch (ExecutionException e) {
            if (e.getCause().getClass() == ProcessStopException.class) {
                throw new ProcessStopException();
            }
            throw new Exception(e);
        }

    }

    //卫星下拉列表 来源于 字典 type = fall_satellite
    //实际执行密度反演
    public AjaxResult invertInner(@RequestBody InversionOrbitVo vo){



        String startTime = vo.getStartTime() + " 00:00:00";
        String endTime = vo.getEndTime() + " 23:59:59";
        String sateName = vo.getSateName();
        String sseId = vo.getSseId();
        //自定义线程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 10, 1,
                TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
                new ThreadPoolExecutor.CallerRunsPolicy());
        try {
            //1.输入数据,starlink卫星陨落数据
            String tableName = "COLLECT_FALL_RECORD";
            List<StOrbitPoint> satOrbitDataLst = dataManagementMapper.getStOrbitData(tableName, startTime, endTime, sateName)
                    .stream()
                    .map(map -> {
                        return new StOrbitPoint( //String epoch, double x, double y, double z, double vx, double vy, double vz
                                // POSITION_X,POSITION_Y,POSITION_Z,SPEED_VX,SPEED_VY,SPEED_VZ
                                DateUtil.formatDateTime((Date) map.get("epoch_time")),
                                Double.parseDouble(map.get("px").toString()),
                                Double.parseDouble(map.get("py").toString()),
                                Double.parseDouble(map.get("pz").toString()),
                                Double.parseDouble(map.get("sx").toString()),
                                Double.parseDouble(map.get("sy").toString()),
                                Double.parseDouble(map.get("sz").toString())
                        );
                    }).collect(Collectors.toList());
            if (CollUtil.isEmpty(satOrbitDataLst)){
                return AjaxResult.error(startTime+" - "+endTime+" 未查询到卫星轨道数据");
            }

            //2.输入数据： 面质比 WIND_AREA / DENS
            List<SpaceTargetMeasurePO> measurePOList = measureService.list(new LambdaQueryWrapper<SpaceTargetMeasurePO>()
                    .eq(SpaceTargetMeasurePO::getName, sateName)
            );
            if (CollUtil.isEmpty(measurePOList)){
                return AjaxResult.error("没有对应本体模型数据，请先新增目标本体模型数据");
            }
            SpaceTargetMeasurePO spaceTargetMeasurePO = measurePOList.get(0);
            String windArea = spaceTargetMeasurePO.getWindArea();
            String dens = spaceTargetMeasurePO.getDens();
            double dSatelliteAreMassRatio = Double.valueOf(windArea) / Double.valueOf(dens);

            //3.输入数据，大气模式
            EAtmosphereModeType iCoordinateType = Arrays.stream(EAtmosphereModeType.values())
                    .filter(o -> o.getValue() == vo.getModelId())
                    .findFirst()
                    .get();

            //异步监控 log文件，实时给前端推送日志
            FileMonitorUtil fileMonitorUtil = new FileMonitorUtil();
            String baseDir = PathUtil.tmpModuleDir(BizModule.DENSITY_INVERT)
                    + "/inversion_orbit_" + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_FORMAT);
            String logFilePath = baseDir + "/log_" + IdUtil.simpleUUID() + ".log";
            String fileEndStr = "end_of_file###end_of_file";
            CompletableFuture.runAsync(() -> {
                fileMonitorUtil.monitoringFileModifyAndSendMsg(logFilePath, null, fileEndStr, sseId);
            });
            fileMonitorUtil.await();

            //调用反演
            List<StAtmosphereDensityData> outDataList = new ArrayList<>();//输出数据之大气密度反演数据

            LowAtmosphereCalcNative.CalcAtmosphereDensityByHpod(satOrbitDataLst,iCoordinateType, sateName, dSatelliteAreMassRatio,logFilePath, outDataList);

             //4. outDataList 存储成文件 -> 调用程雪画图
            if (CollUtil.isEmpty(outDataList)){
                return AjaxResult.error("调用计算结果，返回为空");
            }
            Map<String, List<StAtmosphereDensityData>> groupMap = outDataList.stream()
                    .collect(HashMap<String, List<StAtmosphereDensityData>>::new, (map, entity) -> {

                        String key = entity.epoch.substring(0, 10); // 2006-10-01
                        List<StAtmosphereDensityData> list = map.get(key);
                        if (list == null) {
                            list = new ArrayList<>();
                            list.add(entity);
                            map.put(key, list);
                        } else {
                            list.add(entity);
                        }
                    }, HashMap::putAll);//串行流的第三个combiner不会执行


            String txtDelimiter = ",";
            String txtTitle = "Time,effectAtmosphereResultLon,effectAtmosphereResultLat,effectAtmosphereResultH,effectAtmosphereResultDensityData \n";
            Map<String,String> dayAndPicMap = new ConcurrentHashMap<>();

            CompletableFuture.allOf(groupMap
                    .entrySet()
                    .stream()
                    .map(entry -> {
                        return CompletableFuture.runAsync(() -> {

                            String day = entry.getKey();
                            String txtBody = entry.getValue()
                                    .stream()
                                    .sorted(Comparator.comparing(t -> t.epoch))
                                    .map(entity -> {
                                        return entity.epoch + txtDelimiter
                                                + entity.lon + txtDelimiter
                                                + entity.lat + txtDelimiter
                                                + entity.alt + txtDelimiter
                                                + entity.density;
                                    })
                                    .collect(Collectors.joining("\n"));
                            String txtContent = txtTitle + txtBody;
                            String densityPath = baseDir + "/density_" + day + ".txt";
                            FileWriter.create(new File(densityPath)).write(txtContent);

                            //python new_dataFusion.py SWDATA_OBS_DEN_GRACEB_XXXX.txt /dgdq/tmp/dataFusion/newtest 2005-01-02
                            String codePath = PathUtil.codeModuleDir(BizModule.DENSITY_INVERT) + PathUtil.moduleDirs().getDensityInvert().get("get_pic_path");
                            String command = "python " + codePath + " " + densityPath + " " + baseDir + " " + day;
                            String result = ExecUtil.execCmdWithResult(command);
                            if (result == null || !result.contains("###")) {
                                throw new RuntimeException("调用获取图片脚本时异常，结果: " + (result == null ? "空" : result));
                            }
                            String picPath = result.split("###")[1].trim();
                            dayAndPicMap.put(day, picPath);
                        }, pool);
                    }).toArray(CompletableFuture[]::new)).join();

            //5. 组装数据给前端
            Map<String,Object> resultMap = new HashMap<>();
            //5.1 图片按日期升序返回
            TreeMap<String, String> sortedPicMap = MapUtil.sort(dayAndPicMap);
            resultMap.put("pic", sortedPicMap);

            //5.2 密度数据
            List<String> timeList = outDataList.stream().map(o -> o.epoch).collect(Collectors.toList());
            List<String> densityList = outDataList.stream().map(o -> CommonUtil.doubleToScientific(o.density)).collect(Collectors.toList());
            Map<String, List<String>> densityMap = MapUtil.builder(new HashMap<String, List<String>>())
                    .put("x", timeList)
                    .put("y", densityList)
                    .build();
            resultMap.put("chart", densityMap);

            //准备结果保存
            String densityTxtContentTitle = "Time(UTC),  Altitude(m),  Geodetic LON(degree),  Geodetic LAT(degree),  Density(kg/m3) \n";
            String densityTxtContentBody = outDataList
                    .stream()
                    .sorted(Comparator.comparing(t -> t.epoch))
                    .map(entity -> {
                        return entity.epoch + txtDelimiter
                                + entity.alt + txtDelimiter
                                + entity.lon + txtDelimiter
                                + entity.lat + txtDelimiter
                                + entity.density;
                    })
                    .collect(Collectors.joining("\n"));
            String txtContent = densityTxtContentTitle + densityTxtContentBody;
            String savePathDensity = baseDir + "/density_gps.txt";
            FileWriter.create(new File(savePathDensity)).write(txtContent);

            resultMap.put("resultData", JSONUtil.toJsonStr(Stream.of(savePathDensity).collect(Collectors.toList())));
            resultMap.put("module", ResultSaveModule.DENSITY_INVERT);
            resultMap.put("submodule", ResultSaveModule.DENSITY_INVERT_ORBIT);
            return AjaxResult.success(resultMap);

        }catch (Exception e){
            e.printStackTrace();
            return AjaxResult.error(e.getMessage());
        }finally {
            pool.shutdown();
            //600 终止标志
            SseEmitterUtils.sendMessage(sseId, "600");
            SseEmitterUtils.remove(sseId);
        }
    }

    @ApiOperation(value = "建立连接", notes = "建立连接")
    @GetMapping(path = "subscribe", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
    public SseEmitter push(String id, String flag) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        return SseEmitterUtils.connect(id+loginUser.getUsername(), flag);
    }

}
